#include <cad-slicer/export.h>

#include <fstream>

#include <boost/range/adaptor/reversed.hpp>

#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

#include <cad-slicer/slice.h>


namespace cadslicer
{


bool export_to_svg(std::vector<slice> const& slices,
                   std::array<float, 2> const& box_dimensions,
                   std::array<unsigned, 2> const& img_dim,
                   std::string const& filename)
{
  std::ofstream output(filename);

  if (!output.is_open())
  {
    return false;
  }

  // Set svg header.
  {
    output
        << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
           "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
           "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"
           "<svg "
           "version"    "=\"1.0\" "
           "xml:space"  "=\"preserve\" "
           "xmlns"      "=\"http://www.w3.org/2000/svg\" "
           "xmlns:xlink""=\"http://www.w3.org/1999/xlink\" "
           "x"          "=\"0px\" "
           "y"          "=\"0px\" "
           "width"      "=\"" << img_dim[0] << "px\" "
           "height"     "=\"" << img_dim[1] << "px\">";
  }

  float scale_x = img_dim[0] / box_dimensions[0];
  float scale_y = img_dim[1] / box_dimensions[1];
  float scale = std::min(scale_x, scale_y);

  unsigned i = 0;
  for (slice const& sl : boost::adaptors::reverse(slices))
  {
    output
        << "<g "
           "visibility"  "=\"visible\" "
           "fill"        "=\"red\" "
           "stroke"      "=\"black\" "
           "stroke-width""=\"1\" "
           "id"          "=\"" << i << "\">";

    if (sl.get_contours().size())
    {
      for (slice::hierarchy const* hier :
           sl.get_contour_hierachy().get_children())
      {
        output
            << "<g "
               "visibility"  "=\"visible\" "
               "fill-rule"   "=\"evenodd\" "
               "stroke"      "=\"black\" "
               "stroke-width""=\"1\">";
        output << "<path d=\"";

        std::forward_list<unsigned> all_children =
            hier->all_children();

        for (unsigned id : all_children)
        {
          point2f first = scale * sl.get_contours()[id].front();
          output << "M " << first.real() << "," << first.imag() << " L ";

          for (point2f const& pt : sl.get_contours()[id])
          {
            point2f current = pt * scale;
            output << current.real() << "," << current.imag() << " ";
          }

          output << "z ";
        }

        output << "\"/>";
        output << "</g>";
      }
    }

    output << "</g>";
    ++i;
  }

  // Set svg footer.
  {
    output << "</svg>\n";
  }

  return true;
}


bool export_to_png(slice const& sl,
                   std::array<float, 2> const& box_dimensions,
                   std::array<unsigned, 2> const& img_dim,
                   std::string const& filename)
{
  if (sl.get_contours().begin() == sl.get_contours().end())
    return false;

  cv::Mat img = cv::Mat::zeros(img_dim[0], img_dim[1], CV_8UC3);

  float scale_x = img_dim[0] / box_dimensions[0];
  float scale_y = img_dim[1] / box_dimensions[1];
  float scale = std::min(scale_x, scale_y);

  unsigned char h = 10, s = 120, v = 120;
  for (contour const& contour : sl.get_contours())
  {
    prenstar_assert_gt(contour.size(), 0);
    cv::Scalar color(h,s,v);

    point2f prev = contour.front();
    for (unsigned i = 1; i < contour.size(); ++i)
    {
      point2f curr = contour[i];
      cv::line(img,
               (reinterpret_cast<cv::Point2f const&>(prev)) * scale,
               (reinterpret_cast<cv::Point2f const&>(curr)) * scale,
               color);
      std::swap(prev, curr);
    }

    auto const& first = contour.front();
    cv::line(img,
             (reinterpret_cast<cv::Point2f const&>(prev)) * scale,
             (reinterpret_cast<cv::Point2f const&>(first)) * scale,
             color);

    h += 20;
  }

  cv::cvtColor(img, img, CV_HSV2BGR);
  cv::imwrite(filename, img);
}


bool export_to_svg(
    const cadslicer::slice& sl,
    const std::array<float, 2>& box_dimensions,
    const std::array<unsigned, 2>& img_dim,
    const std::string& filename)
{
  if (!sl.get_contours().size()) return true;

  std::ofstream output(filename);

  if (!output.is_open())
  {
    return false;
  }

  // Set svg header.
  {
    output
        << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
           "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
           "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"
           "<svg "
           "version"    "=\"1.0\" "
           "xml:space"  "=\"preserve\" "
           "xmlns"      "=\"http://www.w3.org/2000/svg\" "
           "xmlns:xlink""=\"http://www.w3.org/1999/xlink\" "
           "x"          "=\"0px\" "
           "y"          "=\"0px\" "
           "width"      "=\"" << img_dim[0] << "px\" "
           "height"     "=\"" << img_dim[1] << "px\">";
  }

  float scale_x = img_dim[0] / box_dimensions[0];
  float scale_y = img_dim[1] / box_dimensions[1];
  float scale = std::min(scale_x, scale_y);

  for (slice::hierarchy const* hier :
       sl.get_contour_hierachy().get_children())
  {
    output
        << "<g "
           "visibility"  "=\"visible\" "
           "fill-rule"   "=\"evenodd\" "
           "stroke"      "=\"black\" "
           "fill"        "=\"red\" "
           "stroke-width""=\"1\">";
    output << "<path d=\"";

    std::forward_list<unsigned> all_children =
        hier->all_children();

    for (unsigned id : all_children)
    {
      point2f first = scale * sl.get_contours()[id].front();
      output << "M " << first.real() << "," << first.imag() << " L ";

      for (point2f const& pt : sl.get_contours()[id])
      {
        point2f current = pt * scale;
        output << current.real() << "," << current.imag() << " ";
      }

      output << "z ";
    }

    output << "\"/>";
    output << "</g>";
  }

  // Set svg footer.
  {
    output << "</svg>\n";
  }

  return true;
}


} // cadslicer
